import { BaseService } from '../framework/base-service.js'
import _ from 'lodash'

export default class CacheService extends BaseService {
  static metadata = {
    name: 'cache',
    dependencies: [],
    singleton: true
  }
  
  constructor() {
    super()
    this.memCache = new Map()
    this.statements = {}
  }
  
  async onInit() {
    this.ttlConfig = this.loadTTLConfig()
    this.prepareStatements()
    this.setupCleanupTask()
    this.logger.info('CacheService', '✅ Cache service initialized')
  }
  
  loadTTLConfig() {
    const tasks = this.config.getTasks()
    
    return _(tasks)
      .mapValues((task, name) => {
        const cron = task.cron
        if (!cron) return 300
        
        const parts = cron.split(' ')
        
        if (parts[0]?.startsWith('*/')) {
          return parseInt(parts[0].substr(2))
        }
        
        if (parts[1]?.startsWith('*/')) {
          return parseInt(parts[1].substr(2)) * 60
        }
        
        if (parts[2]?.startsWith('*/')) {
          return parseInt(parts[2].substr(2)) * 3600
        }
        
        return 300
      })
      .mapKeys((value, key) => `biz.${key}`)
      .value()
  }
  
  prepareStatements() {
    this.statements = {
      get: this.db.prepare(
        'SELECT value, expire_at FROM cache WHERE namespace = ? AND key = ?'
      ),
      set: this.db.prepare(
        'INSERT OR REPLACE INTO cache (namespace, key, value, expire_at, updated_at) VALUES (?, ?, ?, ?, unixepoch())'
      ),
      del: this.db.prepare(
        'DELETE FROM cache WHERE namespace = ? AND key = ?'
      ),
      getAll: this.db.prepare(
        'SELECT key, value, expire_at FROM cache WHERE namespace = ? AND (expire_at IS NULL OR expire_at > unixepoch())'
      ),
      getAllKeys: this.db.prepare(
        'SELECT key FROM cache WHERE namespace = ? AND (expire_at IS NULL OR expire_at > unixepoch())'
      ),
      clear: this.db.prepare(
        'DELETE FROM cache WHERE namespace = ?'
      ),
      clean: this.db.prepare(
        'DELETE FROM cache WHERE expire_at IS NOT NULL AND expire_at < unixepoch()'
      ),
      count: this.db.prepare(
        'SELECT COUNT(*) as count FROM cache WHERE namespace = ?'
      )
    }
  }
  
  setupCleanupTask() {
    setInterval(() => {
      const deleted = this.statements.clean.run()
      if (deleted.changes > 0) {
        console.log(`🧹 Cleaned ${deleted.changes} expired cache entries`)
      }
      
      const memSize = this.memCache.size
      if (memSize > 1000) {
        const toDelete = memSize - 800
        const keys = Array.from(this.memCache.keys())
        _(keys).take(toDelete).forEach(key => this.memCache.delete(key))
        console.log(`🧹 Cleaned ${toDelete} memory cache entries`)
      }
    }, 60000)
  }
  
  async get(namespace, key) {
    const memKey = `${namespace}:${key}`
    
    if (this.memCache.has(memKey)) {
      const cached = this.memCache.get(memKey)
      if (!this.isExpired(cached)) {
        return cached.value
      }
      this.memCache.delete(memKey)
    }
    
    const row = this.statements.get.get(namespace, key)
    if (!row) return null
    
    if (this.isExpired(row)) {
      this.statements.del.run(namespace, key)
      return null
    }
    
    const value = JSON.parse(row.value)
    
    this.memCache.set(memKey, { 
      value, 
      expire_at: row.expire_at 
    })
    
    return value
  }
  
  async set(namespace, key, value, ttl = null) {
    const configTTL = this.ttlConfig[namespace]
    const finalTTL = ttl || configTTL
    const expireAt = finalTTL ? Math.floor(Date.now() / 1000) + finalTTL : null
    
    this.statements.set.run(
      namespace,
      key,
      JSON.stringify(value),
      expireAt
    )
    
    const memKey = `${namespace}:${key}`
    this.memCache.set(memKey, { 
      value, 
      expire_at: expireAt 
    })
    
    this.eventBus.emit(`cache:set:${namespace}`, { key, value })
  }
  
  async del(namespace, key) {
    this.statements.del.run(namespace, key)
    
    const memKey = `${namespace}:${key}`
    this.memCache.delete(memKey)
    
    this.eventBus.emit(`cache:del:${namespace}`, { key })
  }
  
  async getAll(namespace) {
    const rows = this.statements.getAll.all(namespace)
    
    return _(rows)
      .filter(row => !this.isExpired(row))
      .map(row => ({
        key: row.key,
        value: JSON.parse(row.value)
      }))
      .keyBy('key')
      .mapValues('value')
      .value()
  }
  
  async getAllValues(namespace) {
    const rows = this.statements.getAll.all(namespace)
    
    return _(rows)
      .filter(row => !this.isExpired(row))
      .map(row => JSON.parse(row.value))
      .value()
  }
  
  async getAllKeys(namespace) {
    const rows = this.statements.getAllKeys.all(namespace)
    
    return _(rows)
      .map('key')
      .value()
  }
  
  async clear(namespace) {
    const result = this.statements.clear.run(namespace)
    
    const keysToDelete = _(Array.from(this.memCache.keys()))
      .filter(key => key.startsWith(`${namespace}:`))
      .value()
    
    keysToDelete.forEach(key => this.memCache.delete(key))
    
    this.eventBus.emit(`cache:clear:${namespace}`, { 
      count: result.changes 
    })
    
    return result.changes
  }
  
  async updateWithDiff(namespace, newData, keyField = 'CKEY') {
    const oldData = await this.getAllValues(namespace)
    
    const oldMap = _.keyBy(oldData, keyField)
    const newMap = _.keyBy(newData, keyField)
    
    const diff = {
      created: _(newData)
        .filter(item => !oldMap[item[keyField]])
        .value(),
      
      updated: _(newData)
        .filter(item => {
          const old = oldMap[item[keyField]]
          if (!old) return false
          
          const uptime = item.UPTIME || item.UPDATE_TIME || item.UPDATE_AT
          const oldUptime = old.UPTIME || old.UPDATE_TIME || old.UPDATE_AT
          
          if (uptime && oldUptime) {
            return uptime > oldUptime
          }
          
          return !_.isEqual(old, item)
        })
        .value(),
      
      deleted: _(oldData)
        .filter(item => !newMap[item[keyField]])
        .value()
    }
    
    const ttl = this.ttlConfig[namespace]
    const expireAt = ttl ? Math.floor(Date.now() / 1000) + ttl : null
    
    const transaction = this.db.transaction((items) => {
      items.forEach(item => {
        this.statements.set.run(
          namespace,
          item[keyField],
          JSON.stringify(item),
          expireAt
        )
        
        const memKey = `${namespace}:${item[keyField]}`
        this.memCache.set(memKey, {
          value: item,
          expire_at: expireAt
        })
      })
    })
    
    transaction(newData)
    
    _(['created', 'updated', 'deleted']).forEach(action => {
      if (diff[action]?.length > 0) {
        this.eventBus.emit(`${action}:${namespace}`, diff[action])
      }
    })
    
    return diff
  }
  
  isExpired(record) {
    if (!record.expire_at) return false
    return record.expire_at < Math.floor(Date.now() / 1000)
  }
  
  getStats() {
    const namespaces = this.db.prepare(
      'SELECT namespace, COUNT(*) as count FROM cache GROUP BY namespace'
    ).all()
    
    return {
      memoryCache: this.memCache.size,
      sqliteCache: _(namespaces).sumBy('count'),
      namespaces: _(namespaces)
        .keyBy('namespace')
        .mapValues('count')
        .value()
    }
  }
  
  registerRoutes(app) {
    app.get('/api/cache/:namespace', async c => {
      const namespace = c.req.param('namespace')
      const values = await this.getAllValues(namespace)
      return c.json(values)
    })
    
    app.get('/api/cache/:namespace/:key', async c => {
      const { namespace, key } = c.req.param()
      const value = await this.get(namespace, key)
      if (!value) {
        return c.json({ error: 'Not found' }, 404)
      }
      return c.json(value)
    })
    
    app.post('/api/cache/:namespace/:key', async c => {
      const { namespace, key } = c.req.param()
      const value = await c.req.json()
      await this.set(namespace, key, value)
      return c.json({ success: true })
    })
    
    app.delete('/api/cache/:namespace/:key', async c => {
      const { namespace, key } = c.req.param()
      await this.del(namespace, key)
      return c.json({ success: true })
    })
    
    app.delete('/api/cache/:namespace', async c => {
      const namespace = c.req.param('namespace')
      const count = await this.clear(namespace)
      return c.json({ success: true, deleted: count })
    })
    
    app.get('/api/cache', c => {
      const stats = this.getStats()
      return c.json(stats)
    })
    
    app.get('/api/slaver.cache', async c => {
      const { name, cmd, key } = c.req.query()
      
      const handlers = {
        'get': () => this.get(name, key),
        'iterator.all': () => this.getAll(name),
        'iterator.values': () => this.getAllValues(name),
        'values.all': () => this.getAllValues(name),
        'keys': () => this.getAllKeys(name),
        'clear': () => this.clear(name),
        'del': () => this.del(name, key)
      }
      
      const handler = handlers[cmd]
      if (!handler) {
        return c.json({ error: `Unknown command: ${cmd}` }, 400)
      }
      
      try {
        const result = await handler()
        return c.json(result || { success: true })
      } catch (err) {
        return c.json({ error: err.message }, 500)
      }
    })
    
    return true
  }
  
  registerEventListeners(eventBus) {
    eventBus.on('cache:clear', async ({ namespace }) => {
      await this.clear(namespace)
    })
    
    eventBus.on('cache:clean', () => {
      const result = this.statements.clean.run()
      console.log(`🧹 Manual cleanup: ${result.changes} entries`)
    })
  }
  
  registerScheduledTasks(scheduler) {
    scheduler.registerTask('cache-cleanup', {
      cron: '0 */5 * * *',
      handler: () => {
        const result = this.statements.clean.run()
        console.log(`🧹 Scheduled cleanup: ${result.changes} entries`)
        return { deleted: result.changes }
      }
    })
  }
}